package com.example.message_queue.mqserver.datacenter;

import com.example.message_queue.common.BinaryTool;
import com.example.message_queue.common.MqException;
import com.example.message_queue.mqserver.core.MSGQueue;
import com.example.message_queue.mqserver.core.Message;

import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * Created with Intellij IDEA.
 * Description;
 * User:TQ02
 * Data:2024-03-26
 * Time:20:58
 * Text：复制控制队列和消息
 */
public class MessageFileManager {
    //定义一个内部类，表示消息统计的消息
    static public class Stat{
        public int totalCount;
        public int validCount;
    }
    //约定消息文件所在的目录
    private String getQueueDir(String queueName){
        return "./data/"+queueName;
    }

    //获取该队列的消息文件的路径
    private String getQueueDataPath(String queueName){
        return getQueueDir(queueName)+"/queue_data.txt";
    }

    //队列的消息统计文件路径
    private String getQueueStatPath(String queueName){
        return getQueueDir(queueName)+"/queue_stat.txt";

    }

    private Stat readStat(String queueName){
        //由于当前消息是文本文件，可以使用Scanner来读取
        Stat stat=new Stat();
        try(InputStream inputStream=new FileInputStream(getQueueStatPath(queueName))){
            Scanner sc=new Scanner(inputStream);
            stat.totalCount = sc.nextInt();
            stat.validCount = sc.nextInt();
            return stat;
        }catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
    private void writeStart(String queueName,Stat stat){
        //注：OutputStream打开文件，会把原文件清空。
        try(OutputStream outputStream=new FileOutputStream(getQueueStatPath(queueName))){
            PrintWriter printWriter=new PrintWriter(outputStream);
            printWriter.write(stat.totalCount+"\t"+stat.validCount);
            printWriter.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    //创建队列的目录和Stat文件和Data文件
    public void createQueue(String queueName) throws IOException {
        File baseDir=new File(getQueueDir(queueName));
        if(!baseDir.exists()){
            if(!baseDir.mkdirs()){
                throw new IOException("创建目录失败："+baseDir.getAbsolutePath());
            }
        }
        File queueDataDir=new File(getQueueDataPath(queueName));
        if(!queueDataDir.exists()){
            if(!queueDataDir.createNewFile()){
                throw new IOException("创建目录失败："+queueDataDir.getAbsolutePath());
            }
        }
        File queueStat=new File(getQueueStatPath(queueName));
        if(!queueStat.exists()){
            if(!queueStat.createNewFile()){
                throw new IOException("创建目录失败："+queueStat.getAbsolutePath());
            }
        }

        Stat stat=new Stat();
        stat.totalCount=0;
        stat.validCount=0;
        writeStart(queueName,stat);
    }

    //删除队列，消息文件和消息统计删除
    public void deleteQueueFiles(String queueName) throws IOException {
        //先删除文件再删除目录
        File queueDataFile=new File(getQueueDataPath(queueName));
        boolean ok1=queueDataFile.delete();
        File queueStatFile=new File(getQueueStatPath(queueName));
        boolean ok2=queueStatFile.delete();
        File baseDir=new File(getQueueDir(queueName));
        boolean ok3=baseDir.delete();
        if(!ok1 || !ok2 || !ok3) {
            throw new IOException("删除目录和文件失败！" + baseDir.getAbsolutePath());
        }
    }
    //检测队列数据是否存在
    public boolean checkFilesExists(String queueName) {
        File queueStatFile=new File(getQueueStatPath(queueName));
        boolean ok2=queueStatFile.exists();
        File queueDataFile=new File(getQueueDataPath(queueName));
        boolean ok1=queueDataFile.exists();
        if(!ok2 || !ok1){
            System.out.println("队列的2个数据不存在");
            return false;
        }
        return true;
    }

    //将新的消息放到队列对应的文件
    public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {

        //检测该消息队列是否存在
        if(!checkFilesExists(queue.getName())){
            throw new MqException("队列对应的文件不存在："+queue.getName());
        }
        //把Message对象，进行序列化，转换为二进制的文件
        byte[] messageBinary = BinaryTool.toBytes(message);
        synchronized (queue){
            //先获取到队列数据的长度，计算出该Message的偏移量
            //将新的Message数据，写入队列数据文件的末尾，此时此刻的Message 对象offsetBeg，也就是当前文件长度 + 4
            //offsetEnd 就是当前文件长度 + 4 + message 自身长度
            File queueDataFile = new File(getQueueDataPath(queue.getName()));
            message.setOffsetBeg(queueDataFile.length()+4);
            message.setOffsetEnd(queueDataFile.length()+4+messageBinary.length);
            //写入消息到文件
            try (OutputStream outputStream = new FileOutputStream(queueDataFile, true)) {
                try (DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
                    // 接下来要先写当前消息的长度, 占据 4 个字节的~~
                    dataOutputStream.writeInt(messageBinary.length);
                    // 写入消息本体
                    dataOutputStream.write(messageBinary);
                }
            }
            //更新消息统计文件
            Stat  stat=readStat(queue.getName());
            stat.totalCount+=1;
            stat.validCount+=1;
            writeStart(queue.getName(),stat);
        }
    }

    //这里的删除是指逻辑上的删除，valid属性设置为0
    public void deleteMessage(MSGQueue queue,Message message) throws IOException, ClassNotFoundException {

      synchronized (queue) {
          //第一步，将数据读取出来，不可以使用FileInputStream和FileOutStream类，因为这两个都是从头读取
          //使用RandomAccessFile 随机访问
          try (RandomAccessFile random = new RandomAccessFile(getQueueDataPath(queue.getName()), "rw")) {
              byte[] bufferSrc = new byte[(int) (message.getOffsetEnd() - message.getOffsetBeg())];
              random.seek(message.getOffsetBeg());
              random.read(bufferSrc);

              //第二步将isValid改成0
              Message message1 = (Message) BinaryTool.fromBytes(bufferSrc);
              message1.setIsValid((byte) 0x0);
              //第三步，将数据重新写入文件,注意：将光标调整
              byte[] bufferSrc1 = BinaryTool.toBytes(message1);
              random.seek(message.getOffsetBeg());
              random.write(bufferSrc1);
          }
          //第五步，更新统计文件
          Stat stat = readStat(queue.getName());
          if (stat.validCount > 0) {
              stat.validCount -= 1;
          }
          writeStart(queue.getName(), stat);
      }
    }
    //在文件里读取所有的消息内容
    public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {

        LinkedList<Message> messages=new LinkedList<>();
        try(InputStream inputStream=new FileInputStream(getQueueDataPath(queueName))){
            try(DataInputStream dataInputStream = new DataInputStream(inputStream)){
                long currentOffset=0;
                while(true){
                    //readInt()方法读取到末尾时，会抛出EOFException异常借助这个
                    int messageSize = dataInputStream.readInt();   //读取4个字节
                    byte[] buffer=new byte[messageSize];
                    int actualSize = dataInputStream.read(buffer);
                    if(messageSize != actualSize){
                        throw  new MqException("文件格式错误！queue" + queueName);
                    }
                    Message message = (Message) BinaryTool.fromBytes(buffer);
                    if(message.getIsValid() != 0x1){
                        //无效消息
                        currentOffset+=(4+messageSize);
                        continue;
                    }
                    message.setOffsetBeg(currentOffset + 4);
                    message.setOffsetEnd(currentOffset + 4 + messageSize);
                    currentOffset+=(4+messageSize);
                    messages.add(message);
                }
            }catch (EOFException e){
                //
                System.out.println("Message数据展示结束");
            }
        }
        return messages;
    }

    public boolean checkGC(String queueName){
        //判定是否要进行复制算法
        Stat stat=readStat(queueName);
        if(stat.totalCount > 1000 && (double) stat.validCount/(double)stat.totalCount < 0.5){
            return true;
        }
        return false;
    }

    public String getQueueNewPath(String queueName){
        return getQueueDir(queueName)+ "/queue_stat_new.txt";
    }
    public void gc(MSGQueue queue) throws MqException, IOException, ClassNotFoundException {
        synchronized (queue){
            long gcBeg=System.currentTimeMillis();
            //第一步，判断是否需要gc
            if(checkGC(queue.getName())){
                //第二步，创建一个新的文件
                File newDataFile=new File(getQueueNewPath(queue.getName()));
                if(newDataFile.exists()){
                    //正常情况下，不会存在，只可能上一次未删除
                    throw new MqException("gc时发现该队列的queue_data_new已经存在");
                }
                boolean ok=newDataFile.createNewFile();
                if(!ok){
                    throw new MqException("创建文件失败："+queue.getName()+"queue_data_new");
                }
                //第三步，读取之前的有效消息
                LinkedList<Message> messages = loadAllMessageFromQueue(queue.getName());
                //第四步，去无留用
                try(OutputStream outputStream=new FileOutputStream(newDataFile)){
                    try(DataOutputStream dataOutputStream=new DataOutputStream(outputStream)){
                        for(Message message1:messages){
                                byte[] buffer = BinaryTool.toBytes(message1);
                                dataOutputStream.writeInt(4);
                                dataOutputStream.write(buffer);
                        }
                    }
                }
                //第五步，删除原来的数据文件
                File queueDataFile = new File(getQueueDataPath(queue.getName()));
                ok = queueDataFile.delete();
                if(!ok){
                    throw  new MqException("gc旧文件删除失败"+queue.getName());
                }
                ok=newDataFile.renameTo(queueDataFile);
                if(!ok){
                    throw  new MqException("gc新文件命名失败"+queue.getName());
                }
                Stat stat=new Stat();
                stat.totalCount=messages.size();
                stat.validCount= messages.size();
                writeStart(queue.getName(),stat);
            }
            long gcEnd= System.currentTimeMillis();
            System.out.println("gc时间执行完毕"+queue.getName()+"\n时间为："+(gcEnd-gcBeg));
        }

    }

}
